package com.xianggu.spring;


import com.xianggu.mybatis.MapperProxyFactory;
import com.xianggu.mybatis.annotation.Mapper;
import com.xianggu.spring.annotation.*;
import com.xianggu.spring.aop.annotation.*;
import com.xianggu.spring.aop.config.*;
import com.xianggu.spring.aop.enums.AdviceType;
import com.xianggu.spring.aop.enums.PointcutEnum;
import com.xianggu.spring.aop.strategy.PointStrategy;
import com.xianggu.spring.aop.strategy.PointStrategyFactory;
import com.xianggu.spring.aware.BeanNameAware;
import com.xianggu.spring.aware.BeanPostProcessor;
import com.xianggu.spring.aware.InitializingBean;
import com.xianggu.spring.expand.BeanLoadingExtension;
import com.xianggu.spring.expand.ExpandScanFile;
import com.xianggu.spring.expand.ExtensionRegistry;
import com.xianggu.spring.spi.ExpandClassPath;
import com.xianggu.spring.spi.SpringFactoriesLoader;
import com.xianggu.spring.transaction.annotation.Transactional;
import com.xianggu.spring.transaction.proxy.TransactionAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.core.io.DefaultResourceLoader;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author chengjy
 * @date 2025/2/28
 */
public class XiangGuApplicationContent {

    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Object> singletonMap = new ConcurrentHashMap<>();
    private Map<Class<?>, Object> singletonByTypeMap = new HashMap<>();
    // aop：实现接口方式
    private Map<Class<?>, Advice> aopAdviceMap = new HashMap<>();
    // aop: 注解匹配方式
    private Map<PointStrategy, List<AdviceParam>> aopMatchAdviceMap = new HashMap<>();

    private List<BeanPostProcessor> beanPostProcessorsList = new ArrayList<>();
    // requestMapping对象
    private Map<String, Object> contBeanMap = new HashMap<>();

    private ClassLoader classLoader = new DefaultResourceLoader().getClassLoader();

    /**
     * 初始化容器
     * @param configClass
     */
    public XiangGuApplicationContent(Class configClass) {

        // SPI:扫描项目下META-INF文件中的拓展类
        Map<String, List<String>> stringListMap = SpringFactoriesLoader.loadFactories(classLoader);
        if (stringListMap.containsKey(ExpandClassPath.CLASS_PATH_1)) {
            // 获取实现类
            List<String> classPathList = stringListMap.get(ExpandClassPath.CLASS_PATH_1);

            List<BeanLoadingExtension> loadingExtensionList = new ArrayList<>();
            for (String classPath : classPathList) {
                try {
                    Class<?> aClass = classLoader.loadClass(classPath);
                    loadingExtensionList.add((BeanLoadingExtension) aClass.newInstance());
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
            // 注册拓展实现类
            ExtensionRegistry.registerExtension( loadingExtensionList);
        }

        // 需要扫描路径下的所有class对象
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String[] scanPaths = componentScan.paths();//扫描路径

            for (String scanPath : scanPaths) {
                URL resource = classLoader.getResource(scanPath.replace(".", "/"));

                // 创建扫描路径下所有的文件夹
                File file = new File(resource.getFile());
                // 递归方法
                doScanner(file, scanPath);
            }
        }
        try {
            if(ExpandScanFile.class.isAssignableFrom( configClass)){
                // 扩展扫描
                ExpandScanFile scanClass = (ExpandScanFile) configClass.newInstance();

                URL resource = scanClass.doScanner(classLoader);
                // 创建扫描路径下所有的文件夹
                File file = new File(resource.getFile());
                // 递归方法
                doScanner(file, scanClass.getScanPath());
            }
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }


        // 创建bean
        for (Map.Entry<String, BeanDefinition> definitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = definitionEntry.getKey();
            BeanDefinition beanDefinition = definitionEntry.getValue();

            if ("singleton".equals(beanDefinition.getScope())) {
                Object o = createBean(beanName, beanDefinition);
                // 放入单例池
                singletonMap.put(beanName, o);
                // 放入类型单例池
                singletonByTypeMap.put(beanDefinition.getBeanClass(), o);
            }
        }
        System.out.println();
    }

    /**
     * 递归方法（扫描路径下所有的class 并生成BeanDefinition对象）
     * @param file
     * @param scanPath 包截取路径
     */
    private void doScanner(File file, String scanPath) {
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                doScanner(f, scanPath);
            }
        }else{
            //只获取class文件
            if (file.getName().endsWith(".class")) {
                // 反射生成class文件，判断有没有包含bean注解
                String beanPath = "";
                try {
                    String absolutePath = file.getAbsolutePath();
                    beanPath = absolutePath.substring(absolutePath.indexOf(scanPath.replace(".", "\\"))
                                    , absolutePath.indexOf(".class"))
                            .replace("\\", ".");
                    Class<?> clazz = classLoader.loadClass(beanPath);

                    /* 提供一个拓展方法，返回bool值，为真则创建BeanDefinition对象*/
                    BeanLoadingExtension loadingExtension = ExtensionRegistry.satisfyCondition(clazz);
                    if (loadingExtension != null) {
                        String beanName =  loadingExtension.getBeanName(clazz);
                        createBeanDefinition(clazz, beanName);
                        return;
                    }

                    // 标识Bean注解
                    if (clazz.isAnnotationPresent(Component.class) ) {
                        Component annotation = clazz.getAnnotation(Component.class);
                        String annotationValue = annotation.value();
                        createBeanDefinition2(annotationValue, clazz);

                        // 获取AOP切面接口类
                        if (Advice.class.isAssignableFrom(clazz)){
                            Advice object = (Advice) clazz.newInstance();
                            try{
                                Class<?> poinitAnnotation = classLoader.loadClass(object.Pointcut());
                                aopAdviceMap.put( poinitAnnotation , object);
                            }catch (ClassNotFoundException e){
                                throw new RuntimeException("切面类中Pointcut注解的值不存在" + e);
                            }
                        }
                    } else if ( clazz.isAnnotationPresent(Service.class)) {
                        Service annotation = clazz.getAnnotation(Service.class);
                        String annotationValue = annotation.value();
                        createBeanDefinition2(annotationValue, clazz);
                    }else if ( clazz.isAnnotationPresent(Mapper.class)) {
                        Mapper annotation = clazz.getAnnotation(Mapper.class);
                        String annotationValue = annotation.value();
                        BeanDefinition beanDefinition = createBeanDefinition2(annotationValue, clazz);
                        beanDefinition.setMapper(true);// 标识Mapper
                    }else if(clazz.isAnnotationPresent(Controller.class)){
                        Controller annotation = clazz.getAnnotation(Controller.class);
                        String annotationValue = annotation.value();
                        BeanDefinition beanDefinition = createBeanDefinition2(annotationValue, clazz);
                        beanDefinition.setController(true); // 标识Controller控制器
                    }else if(clazz.isAnnotationPresent(Aspect.class)){
                        for (Method declaredMethod : clazz.getDeclaredMethods()) {
                            // 识别对应通知的切面注解
                            if (declaredMethod.isAnnotationPresent(After.class)) {
                                After pointAnnotation = declaredMethod.getAnnotation(After.class);
                                // 切入点方法名称
                                String pointMethodName = pointAnnotation.value();
                                // 切面注解类型
                                Class<? extends Annotation> annoType = pointAnnotation.annotationType();
                                // 收集切面方法
                                matchPointMethod(declaredMethod, pointMethodName, clazz, annoType);
                            } else if (declaredMethod.isAnnotationPresent(Before.class)) {
                                Before pointAnnotation = declaredMethod.getAnnotation(Before.class);
                                String pointMethodName = pointAnnotation.value();
                                Class<? extends Annotation> annoType = pointAnnotation.annotationType();
                                matchPointMethod(declaredMethod, pointMethodName, clazz, annoType);
                            } else if (declaredMethod.isAnnotationPresent(AfterReturning.class)) {
                                AfterReturning pointAnnotation = declaredMethod.getAnnotation(AfterReturning.class);
                                String pointMethodName = pointAnnotation.value();
                                Class<? extends Annotation> annoType = pointAnnotation.annotationType();
                                matchPointMethod(declaredMethod, pointMethodName, clazz, annoType);
                            } else if (declaredMethod.isAnnotationPresent(AfterThrowing.class)) {
                                AfterThrowing pointAnnotation = declaredMethod.getAnnotation(AfterThrowing.class);
                                String pointMethodName = pointAnnotation.value();
                                Class<? extends Annotation> annoType = pointAnnotation.annotationType();
                                matchPointMethod(declaredMethod, pointMethodName, clazz, annoType);
                            } else if (declaredMethod.isAnnotationPresent(Around.class)) {
                                Around pointAnnotation = declaredMethod.getAnnotation(Around.class);
                                String pointMethodName = pointAnnotation.value();
                                Class<? extends Annotation> annoType = pointAnnotation.annotationType();
                                matchPointMethod(declaredMethod, pointMethodName, clazz, annoType);
                            }
                        }
                    }

                } catch (ClassNotFoundException e) {
                    System.out.println("反射失败！[" + beanPath + "] " + e);
                    throw new RuntimeException(e);
                } catch (InvocationTargetException e) {
                    System.out.println("反射失败！生成BeanPostProcessor 失败");
                    throw new RuntimeException(e);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 收集切面方法
     * @param declaredMethod 当前bean方法
     * @param pointMethodName 切入点方法
     * @param clazz 当前bean Class对象
     * @param annoType 切入点注解类型
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void matchPointMethod(Method declaredMethod, String pointMethodName, Class<?> clazz, Class<? extends Annotation> annoType) throws NoSuchMethodException, InstantiationException, IllegalAccessException {
        if(pointMethodName.contains("()")){
            pointMethodName = pointMethodName.replace("()","");
        }
        Method pointMethod = clazz.getDeclaredMethod(pointMethodName);
        if(pointMethod.isAnnotationPresent( Pointcut.class)){
            // 切入点方法
            Pointcut annotation = pointMethod.getAnnotation(Pointcut.class);
            PointcutEnum pointType = annotation.value();
            PointStrategy pointStrategy = PointStrategyFactory.getPointStrategy(pointType , annotation.path());
            // 创建切入点实体对象
            AdviceParam adviceParam = new AdviceParam(clazz.newInstance(), declaredMethod, null, AdviceType.getAdviceType(annoType));
            if (aopMatchAdviceMap.containsKey( pointStrategy)) {
                aopMatchAdviceMap.get(pointStrategy).add(adviceParam);
            }else{
                List<AdviceParam> adviceParamList = new ArrayList<>();
                adviceParamList.add(adviceParam);
                aopMatchAdviceMap.put(pointStrategy, adviceParamList);
            }
        }else{
            throw new RuntimeException("切面方法没有定义切入点");
        }
    }

    private BeanDefinition createBeanDefinition2(String annotationValue, Class<?> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        // 获取beanName
        String beanName = "".equals(annotationValue) ? Introspector.decapitalize(clazz.getSimpleName()) : annotationValue;
        //类是不是由这个类派生的，有没有实现这个接口
        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
            // 将通知处理器bean加入到集合中
            beanPostProcessorsList.add((BeanPostProcessor)
                    clazz.getDeclaredConstructor().newInstance());

        }
        // 生成beanDefinition对象
        return createBeanDefinition(clazz, beanName);
    }

    private BeanDefinition createBeanDefinition(Class<?> clazz, String beanName) {
        // 生成 beanDefinition对象，放入到容器中
        BeanDefinition beanDefinition = new BeanDefinition();

        // 判断是不是单例
        if (clazz.isAnnotationPresent(Scope.class)) {
            String type = clazz.getAnnotation(Scope.class).value();
            beanDefinition.setScope("prototype".equals(type) ? "prototype" : "prototype");
        } else {
            beanDefinition.setScope("singleton");
        }
        // 设置bean类型
        beanDefinition.setBeanClass(clazz);
        beanDefinition.setBeanName(beanName);
        // 将bean放入单例池
        beanDefinitionMap.put(beanName, beanDefinition);
        return beanDefinition;
    }

    /**
     * 创建bean对象方法：（生命周期）
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 单独给Mybatis的mapper生成sql代理对象
        if (beanDefinition.isMapper()) {
            return MapperProxyFactory.getMapperr(beanClass);
        }

        // 接口特殊处理
        if (beanClass.isInterface()) {
            // 需要忽略自身的beandefinition，只找实现类的
            List<BeanDefinition> beans = beanDefinitionMap.values().stream()
                    .filter(bean -> beanClass.isAssignableFrom(bean.getBeanClass()) && !bean.getBeanClass().equals(beanClass)  ).collect(Collectors.toList());
            // 获取到多个 beanDefinitions 类型时 判断
            if (!beans.isEmpty()) {
                BeanDefinition bd1 = beans.get(0);
                return createBean(bd1.getBeanName(), bd1);
            }
            // 说明找不到 则继续执行
        }

        // 单独给Feign客户端生成代理对象
        Object expandObj = ExtensionRegistry.executeCreateBean(beanName, beanDefinition);
        if(expandObj != null) {
            return expandObj;
        }


        // 获取无参构造方法
        try {
            // 下面代码不支持接口代理
            Object o = beanClass.getConstructors()[0].newInstance();

            // AOP拦截处理: 实现接口方式
            Set<Class<?>> classes = aopAdviceMap.keySet();
            for (Class<?> aClass : classes) {
                // 判断是否需要拦截
                if (beanClass.isAnnotationPresent((Class<? extends Annotation>) aClass)) {
                    // 获取拦截器
                    Advice advice = aopAdviceMap.get(aClass);
                    // 判断是否是接口，从而选择对应的代理器
                    if(beanClass.isInterface()){
                        // JDK动态代理
                        o = AopJdkProxyFactory.getProxy(o, advice);
                    }else{
                        AopCglibProxyFactory adviceDemo = new AopCglibProxyFactory(beanClass , advice);
                        Enhancer enhancer = new Enhancer();
                        // 设置父类（目标类）
                        enhancer.setSuperclass(beanClass);
                        // 设置回调拦截器
                        enhancer.setCallback(adviceDemo);

                        o = enhancer.create();
                    }
                }
            }

            // AOP 注解匹配方法
            if (!aopMatchAdviceMap.isEmpty()) {
                for (PointStrategy pointStrategy : aopMatchAdviceMap.keySet()) {
                    List<AdviceParam> adviceParams = aopMatchAdviceMap.get(pointStrategy);
                    // 无需执行代理的方法名
                    List<String> noProxyMethods = new ArrayList<>();
                    if (pointStrategy.match(beanClass , noProxyMethods)) {
                        // 判断是否是接口，从而选择对应的代理器
                        if(beanClass.isInterface()){
                            // JDK动态代理
                            o = AopJdkProxyFactory.getProxy(o, adviceParams, noProxyMethods);
                        }else{
                            AopCglibProxy2Factory adviceDemo = new AopCglibProxy2Factory(beanClass , adviceParams , noProxyMethods);
                            Enhancer enhancer = new Enhancer();
                            // 设置父类（目标类）
                            enhancer.setSuperclass(beanClass);
                            // 设置回调拦截器
                            enhancer.setCallback(adviceDemo);
                            o = enhancer.create();
                        }
                    }
                }
            }

            // 属性注入
            for (Field field : beanClass.getDeclaredFields()) {
                // byName
                if (field.isAnnotationPresent(AutoWired.class)) {
                    AutoWired annotation = field.getAnnotation(AutoWired.class);
                    field.setAccessible(true);
                    // beanName
                    String name = "".equals(annotation.value()) ?  Introspector.decapitalize(field.getName()) : annotation.value();
                    Class<?> aClass = field.getType();
                    field.set(o, getBean(name , aClass));
                }
                // byType
                if (field.isAnnotationPresent(Resource.class)) {
                    Resource annotation = field.getAnnotation(Resource.class);
                    field.setAccessible(true);
                    // beanName
                    String name = "".equals(annotation.value()) ?  Introspector.decapitalize(field.getName()) : annotation.value();
                    Class<?> aClass = field.getType();
                    field.set(o, getBeanByType(name , aClass));
                }
            }
            boolean isTransaction = false;
            for (Method method : beanClass.getMethods()) {
                if (method.isAnnotationPresent(PostConstruct.class)) {
                    method.setAccessible(true);
                    method.invoke(o);
                }
                if (method.isAnnotationPresent(Transactional.class)) {
                    isTransaction = true;
                }
            }

            if (isTransaction) {
                // 无需执行代理的方法名
                List<String> noProxyMethods = new ArrayList<>();
                for (Method method : beanClass.getMethods()) {
                    if (method.isAnnotationPresent(Transactional.class)) {
                        noProxyMethods.add(method.getName());
                    }
                }

                // 创建事务代理对象
                TransactionAspect transactionAspect = new TransactionAspect(o);
                o = transactionAspect.createProxy(noProxyMethods);
            }

            // 前置通知
            for (BeanPostProcessor beanPostProcessor : this.beanPostProcessorsList) {
                o = beanPostProcessor.postProcessBeforeInitialization(beanName, o);
            }

            // Aware回调
            if (o instanceof BeanNameAware) {
                ((BeanNameAware) o).setBeanName(beanName);
            }
            // 执行初始化逻辑
            if (o instanceof InitializingBean) {
                ((InitializingBean) o).afterPropertiesSet();
            }

            // 后置通知
            for (BeanPostProcessor beanPostProcessor : this.beanPostProcessorsList) {
                o = beanPostProcessor.postProcessAfterInitialization(beanName, o);
            }

            // 收集处理器对象
            if (beanDefinition.isController()) {
                if(beanClass.isAnnotationPresent(RequestMapping.class)){
                    RequestMapping annotation = beanClass.getAnnotation(RequestMapping.class);
                    // 请求路径
                    String reqPath = annotation.value();
                    if(reqPath != null && !reqPath.isEmpty()){
                        //清楚路径的首斜杠
                        if (reqPath.startsWith("/")) {
                            reqPath = reqPath.substring(1, reqPath.length());
                        }
                        contBeanMap.put( reqPath , o);
                    }
                }
            }
            return o;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取bean方法
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        // 从单例池中获取beanDefinition
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new RuntimeException(beanName + "bean 不存在！");
        }
        // beanDefinition 判断是单例否
        if ("singleton".equals(beanDefinition.getScope())) {
            Object o = this.singletonMap.get(beanName);
            if (o == null) {
                // 属性注入时可能未初始化，需要重新生成
                o = createBean(beanName, beanDefinition);
                singletonMap.put(beanName, o);
            }
            return o;
        } else {
            return createBean(beanName, beanDefinition);
        }
    }

    /**
     * 获取bean方法
     *
     * @param beanName
     * @param aClass
     * @return
     */
    private Object getBean(String beanName, Class<?> aClass) {
        // 从单例池中获取beanDefinition
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            // 根据名称找不到对象，尝试根据类型找
            Object beanByType = getBeanByType(beanName, aClass);
            if (beanByType != null) {
                return beanByType;
            }
            throw new RuntimeException(beanName + "bean 不存在！");
        }
        // beanDefinition 判断是单例否
        if ("singleton".equals(beanDefinition.getScope())) {
            Object o = this.singletonMap.get(beanName);
            if (o == null) {
                // 属性注入时可能未初始化，需要重新生成
                o = createBean(beanName, beanDefinition);
                singletonMap.put(beanName, o);
            }
            return o;
        } else {
            return createBean(beanName, beanDefinition);
        }
    }

    private Object getBeanByType(String beanName , Class<?> className) {
        // 遍历beanDefinitionMap 获取对应类型的class
        Class<?> finalClassName = className;
        List<BeanDefinition> beanDefinitions = beanDefinitionMap.values().stream()
                .filter(beanDefinition -> beanDefinition.getBeanClass().equals(finalClassName) || className.isAssignableFrom(beanDefinition.getBeanClass()) ).collect(Collectors.toList());
        // 获取到多个 beanDefinitions 类型时 判断
        if (beanDefinitions.size() > 1) {
            throw new RuntimeException(className + " 根据类型查找bean时获取到多个bean，请检查[" + beanDefinitions +"]");
        }
        if (beanDefinitions.size() == 0) {
            throw new RuntimeException(className + " 根据类型查找bean时未获取到bean");
        }
        // 获取到唯一的BeanDefinition
        BeanDefinition beanDefinition = beanDefinitions.get(0);
        if (beanDefinition == null) {
            throw new RuntimeException(beanName + "bean 不存在！");
        }
        // beanDefinition 判断是单例否
        if ("singleton".equals(beanDefinition.getScope())) {
            Object o = this.singletonByTypeMap.get(className);
            if (o == null) {
                // 属性注入时可能未初始化，需要重新生成
                o = createBean(beanName, beanDefinition);
                // 补齐两边单例池
                singletonMap.put(beanName, o);
                singletonByTypeMap.put(className, o);
            }
            return o;
        } else {
            return createBean(beanName, beanDefinition);
        }
    }

    public Map<String, Object> getContBeanMap() {
        return contBeanMap;
    }
}
